package com.alan.product.issue.util;

import lombok.extern.slf4j.Slf4j;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;

/**
 * Snowflake算法是带有时间戳的全局唯一ID生成算法。它有一套固定的ID格式，如下：
 *
 * <p>41位的时间序列（精确到毫秒，41位的长度可以使用69年） 10位的机器标识（10位的长度最多支持部署1024个节点）
 * 12位的Sequence序列号（12位的Sequence序列号支持每个节点每毫秒产生4096个ID序号）
 *
 * <p>结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * 优点是：整体上按照时间自增排序，且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分)  <br>
 */
@Slf4j
public class SnowflakeIdWorker {

    private static final ThreadLocal<SimpleDateFormat> SIMPLE_DATE_FORMAT_THREAD_LOCAL
            = ThreadLocal.withInitial(()->new SimpleDateFormat("yyMMddHHmmssSSS"));
    /**
     * 开始时间截 (从2015-01-01起)
     */
    private static final long START_TIME = 1420041600000L;
    /**
     * 机器ID所占位数
     */
    private static final long ID_BITS = 5L;
    /**
     * 数据中心ID所占位数
     */
    private static final long DATA_CENTER_ID_BITS = 5L;

    /**
     * 机器ID最大值16 (此移位算法可很快计算出n位二进制数所能表示的最大十进制数)
     */
    private static final long MAX_ID = ~(-1L << ID_BITS);

    /**
     * 数据中心ID最大值2
     */
    private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
    /**
     * Sequence所占位数
     */
    private static final long SEQUENCE_BITS = 12L;
    /**
     * 机器ID偏移量4
     */
    private static final long ID_SHIFT_BITS = SEQUENCE_BITS;
    /**
     * 数据中心ID偏移量
     */
    private static final long DATA_CENTER_ID_SHIFT_BITS = SEQUENCE_BITS + ID_BITS;
    /**
     * 时间戳的偏移量
     */
    private static final long TIMESTAMP_LEFT_SHIFT_BITS =  SEQUENCE_BITS + ID_BITS + DATA_CENTER_ID_BITS;
    /**
     * Sequence掩码 19
     */
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
    /**
     * 时间掩码偏移量
     */
    private static final long TIMESTAMP_BITS = 11;
    /**
     * 时间掩码 0xfffffffffffffc00 秒钟
     */
    private static final long TIMESTAMP_MASK = -1L << TIMESTAMP_BITS;
    /**
     *  上一毫秒数
     */
    private static long lastTimestamp = -1;
    /**
     * 毫秒内Sequence(0~16)
     */
    private static long sequence = 0L;
    /**
     * 机器ID(0-16)
     */
    private final long workerId;
    /**
     * 数据中心ID(0-2)
     */
    private final long dataCenterId;

    private static boolean dataCenterIdInitialized = false;

    private static volatile SnowflakeIdWorker snowflake_Id_Worker;

    static {
        snowflake_Id_Worker = new SnowflakeIdWorker();
    }

    public SnowflakeIdWorker(){
        this.dataCenterId = getDataCenterId(MAX_DATA_CENTER_ID);
        this.workerId = getMaxWorkerId(dataCenterId, MAX_ID);
        log.info("SnowflakeIdWorker:Init dataCenterId:{} workerId:{}", dataCenterId, workerId);
    }


    /**
     * 构造
     * @param workerId 机器ID
     * @param dataCenterId 数据中心ID
     */
    public SnowflakeIdWorker(long workerId, long dataCenterId) {
        if (workerId > MAX_ID || workerId < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0", MAX_ID));
        }
        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
            throw new IllegalArgumentException(
                    String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATA_CENTER_ID));
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

    /**
     * 生成ID（线程安全）
     */
    public long nextId() {
        long timestamp = timeGen();
        // 如果当前时间小于上一次ID生成的时间戳，说明系统时钟被修改过，回退在上一次ID生成时间之前应当抛出异常！！！
        if (timestamp < lastTimestamp) {
            throw new IllegalStateException(String.format("Clock moved backwards.  Refusing to generate id for {} milliseconds",lastTimestamp- timestamp));
        }
        // 如果是同一时间生成的，则进行毫秒内sequence生成
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            // 溢出处理
            if (sequence == 0) {
                // 阻塞到下一毫秒,获得新时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else { // 时间戳改变，毫秒内sequence重置
            sequence = ThreadLocalRandom.current().nextLong(1000);
        }
        // 上次生成ID时间截
        lastTimestamp = timestamp;
        // 移位并通过或运算组成64位ID
        return  (dataCenterId << DATA_CENTER_ID_SHIFT_BITS)
                | (workerId << ID_SHIFT_BITS)
                | sequence;
    }

    /**
     * 阻塞到下一毫秒,获得新时间戳
     *
     * @param lastTimestamp 上次生成ID时间截
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 获取以毫秒为单位的当前时间
     *
     * @return 当前时间(秒)
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }/**
     * <p>
     * 获取 maxWorkerId
     * </p >
     */
    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
        StringBuffer mpid = new StringBuffer();
        mpid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (!name.isEmpty()) {
            /*
             * GET jvmPid
             */
            mpid.append(name.split("@")[0]);
        }
        mpid.append(ThreadLocalRandom.current().nextLong(1000));
        mpid.append(ThreadLocalRandom.current().nextLong(1000));
        /*
         * MAC + PID 的 hashcode 获取16个低位
         */
        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }

    /**
     * 通过hostname初始化机器ID, 将hostname各字符累加并对machineId最大值取余
     * <p>虚拟主机: 可手工设置, eg:prod1 </p >
     * <p>普通容器: 默认容器ID为HOSTNAME, eg: b88598ebca38</p >
     * <p>K8s Pod: 默认Pod ID为HOSTNAME, eg: user-service-433cf-5f6d48c7f4-8fclh</p >
     * </p >
     */
    protected static long getDataCenterId(long maxDataCenterId) {
        long id = 0L;
        try {
            if (!dataCenterIdInitialized) {
                String hostName = InetAddress.getLocalHost().getHostName();
                char[] hostNameChars = hostName.toCharArray();
                int sumOfHostname = 0;
                for (int i = 0, length = hostNameChars.length; i < length; i++) {
                    sumOfHostname += hostNameChars[i];
                }
                id = sumOfHostname % maxDataCenterId;
                log.info("SnowflakeIdWorker:Init dataCenterId. hostName[" + hostName + "], dataCenterId[" + id + "]");
            }
        } catch (Exception e) {
            log.info("getDataCenterId: error={} " + e.getMessage());
        } finally {
            dataCenterIdInitialized = true;
        }
        return id;
    }

    public static synchronized String genSnowIdString() {
        long id = snowflake_Id_Worker.nextId();
        Date date = new Date(lastTimestamp);
        String second = SIMPLE_DATE_FORMAT_THREAD_LOCAL.get().format(date);
        return String.format("%s%07d", second, id);
    }

}
